#!/bin/bash
###############################################################################
#       脚本名称: properties.sh
#       功能描述: Java Properties配置文件操作模块
#       作者: 林再来
#       日期: 2025-10-14
#       版本: 1.0
#       功能:
#           1、Properties文件读取、写入、删除操作
#           2、Properties格式验证
#           3、Properties文件合并
#           4、哈希计算和变更检测
#           5、支持层次结构(key.subkey)和国际化
#       依赖:
#           1、纯Shell + awk实现，无外部依赖。
###############################################################################

# 注意：不使用严格模式，避免影响交互式 shell

###############################################################################
# 格式说明
# Properties文件格式:
# - key=value 或 key:value 或 key value
# - 支持 # 或 ! 开头的注释
# - 支持多行值（行尾\续行）
# - 支持Unicode转义 \uXXXX
# - 支持层次结构 app.database.host=localhost
###############################################################################

###############################################################################
# 核心功能
###############################################################################

# 函数: 验证Properties文件格式
properties_validate() {
    local file="$1"
    
    if [[ ! -f "$file" ]]; then
        log_error "properties" "文件不存在: $file"
        return 1
    fi
    
    # 检查是否有有效的键值对
    if grep -qE '^[^#!][a-zA-Z0-9._-]+\s*[=: ]' "$file" 2>/dev/null; then
        return 0
    else
        log_error "properties" "文件格式无效: $file"
        return 1
    fi
}

# 函数: 读取Properties值
properties_read() {
    local file="$1"
    local key="$2"
    local default="${3:-}"
    
    if [[ ! -f "$file" ]]; then
        echo "$default"
        return 1
    fi
    
    # 使用awk读取，支持=、:、空格分隔符
    local value=$(awk -F'[=: ]' -v key="$key" '
        # 跳过注释和空行
        /^[#!]/ || /^$/ { next }
        
        # 处理续行
        /\\$/ {
            line = $0
            sub(/\\$/, "", line)
            getline nextline
            $0 = line nextline
        }
        
        # 匹配键
        $1 == key {
            # 获取值部分（处理多个分隔符）
            sub(/^[^=:]*[=: ]*/, "")
            # 去除首尾空格
            gsub(/^[ \t]+|[ \t]+$/, "")
            # 去除引号
            gsub(/^["'"'"']|["'"'"']$/, "")
            print
            exit
        }
    ' "$file")
    
    echo "${value:-$default}"
}

# 函数: 写入Properties值
properties_write() {
    local file="$1"
    local key="$2"
    local value="$3"
    local separator="${4:-=}"  # 分隔符: = 或 : 或空格
    
    # 确保文件存在
    touch "$file"
    
    # 创建备份
    cp "$file" "${file}.bak"
    
    # 转义特殊字符
    local escaped_value="$value"
    escaped_value="${escaped_value//\\/\\\\}"  # 反斜杠
    
    # 检查键是否存在
    if grep -qE "^${key}\s*[=: ]" "$file" 2>/dev/null; then
        # 更新现有键
        awk -v key="$key" -v value="$escaped_value" -v sep="$separator" '
            BEGIN { updated = 0 }
            /^[#!]/ || /^$/ { print; next }
            $0 ~ "^" key "[=: ]" && !updated {
                print key sep value
                updated = 1
                next
            }
            { print }
        ' "${file}.bak" > "$file"
    else
        # 追加新键
        echo "${key}${separator}${escaped_value}" >> "$file"
    fi
    
    rm -f "${file}.bak"
}

# 函数: 删除Properties键
properties_delete() {
    local file="$1"
    local key="$2"
    
    if [[ ! -f "$file" ]]; then
        return 1
    fi
    
    # 创建备份
    cp "$file" "${file}.bak"
    
    # 删除匹配的行
    awk -v key="$key" '
        /^[#!]/ || /^$/ { print; next }
        $0 !~ "^" key "[=: ]" { print }
    ' "${file}.bak" > "$file"
    
    rm -f "${file}.bak"
}

# 函数: 列出所有键
properties_list_keys() {
    local file="$1"
    local prefix="${2:-}"  # 可选的键前缀过滤
    
    if [[ ! -f "$file" ]]; then
        return 1
    fi
    
    awk -v prefix="$prefix" '
        /^[#!]/ || /^$/ { next }
        /^[a-zA-Z0-9._-]+\s*[=: ]/ {
            match($0, /^[a-zA-Z0-9._-]+/)
            key = substr($0, RSTART, RLENGTH)
            if (prefix == "" || index(key, prefix) == 1) {
                print key
            }
        }
    ' "$file" | sort -u
}

# 函数: 列出所有键值对
properties_list_all() {
    local file="$1"
    
    if [[ ! -f "$file" ]]; then
        return 1
    fi
    
    awk '
        /^[#!]/ || /^$/ { next }
        /^[a-zA-Z0-9._-]+\s*[=: ]/ {
            # 分离键和值
            match($0, /^[a-zA-Z0-9._-]+/)
            key = substr($0, RSTART, RLENGTH)
            sub(/^[^=:]*[=: ]*/, "")
            gsub(/^[ \t]+|[ \t]+$/, "")
            print key "=" $0
        }
    ' "$file"
}

# 函数: 合并Properties文件
properties_merge() {
    local target="$1"
    local source="$2"
    local mode="${3:-merge}"  # merge | overwrite
    
    if [[ ! -f "$source" ]]; then
        log_error "properties" "源文件不存在: $source"
        return 1
    fi
    
    # 确保目标文件存在
    touch "$target"
    
    # 创建备份
    cp "$target" "${target}.bak"
    
    # 读取源文件的所有键值对
    while IFS='=' read -r key value; do
        [[ -z "$key" ]] && continue
        
        if [[ "$mode" == "overwrite" ]]; then
            # 覆盖模式：直接写入
            properties_write "$target" "$key" "$value"
        else
            # 合并模式：只添加不存在的键
            if ! grep -qE "^${key}\s*[=: ]" "$target" 2>/dev/null; then
                properties_write "$target" "$key" "$value"
            fi
        fi
    done < <(properties_list_all "$source")
    
    rm -f "${target}.bak"
}

# 函数: 格式化Properties文件
properties_format() {
    local file="$1"
    local align="${2:-false}"  # 是否对齐等号
    
    if [[ ! -f "$file" ]]; then
        return 1
    fi
    
    # 创建备份
    cp "$file" "${file}.bak"
    
    if [[ "$align" == "true" ]]; then
        # 对齐等号
        awk '
            /^[#!]/ || /^$/ { print; next }
            /^[a-zA-Z0-9._-]+\s*[=: ]/ {
                match($0, /^[a-zA-Z0-9._-]+/)
                key = substr($0, RSTART, RLENGTH)
                sub(/^[^=:]*[=: ]*/, "")
                gsub(/^[ \t]+|[ \t]+$/, "")
                printf "%-40s = %s\n", key, $0
                next
            }
            { print }
        ' "${file}.bak" > "$file"
    else
        # 标准格式化：统一使用=，去除多余空格
        awk '
            /^[#!]/ || /^$/ { print; next }
            /^[a-zA-Z0-9._-]+\s*[=: ]/ {
                match($0, /^[a-zA-Z0-9._-]+/)
                key = substr($0, RSTART, RLENGTH)
                sub(/^[^=:]*[=: ]*/, "")
                gsub(/^[ \t]+|[ \t]+$/, "")
                print key "=" $0
                next
            }
            { print }
        ' "${file}.bak" > "$file"
    fi
    
    # 删除多余空行
    sed -i.tmp '/^$/N;/^\n$/d' "$file"
    rm -f "${file}.tmp"
    
    rm -f "${file}.bak"
}

# 函数: 提取指定前缀的配置（用于模块化）
properties_extract() {
    local file="$1"
    local prefix="$2"
    local output="${3:-}"
    
    if [[ ! -f "$file" ]]; then
        return 1
    fi
    
    local result=$(awk -v prefix="$prefix" '
        /^[#!]/ || /^$/ { next }
        /^[a-zA-Z0-9._-]+\s*[=: ]/ {
            match($0, /^[a-zA-Z0-9._-]+/)
            key = substr($0, RSTART, RLENGTH)
            if (index(key, prefix) == 1) {
                print $0
            }
        }
    ' "$file")
    
    if [[ -n "$output" ]]; then
        echo "$result" > "$output"
    else
        echo "$result"
    fi
}

# 函数: 转换为环境变量格式
properties_to_env() {
    local file="$1"
    local output="${2:-}"
    
    if [[ ! -f "$file" ]]; then
        return 1
    fi
    
    local result=$(awk '
        /^[#!]/ || /^$/ { next }
        /^[a-zA-Z0-9._-]+\s*[=: ]/ {
            match($0, /^[a-zA-Z0-9._-]+/)
            key = substr($0, RSTART, RLENGTH)
            sub(/^[^=:]*[=: ]*/, "")
            gsub(/^[ \t]+|[ \t]+$/, "")
            
            # 转换键名：点号转下划线，转大写
            env_key = key
            gsub(/\./, "_", env_key)
            env_key = toupper(env_key)
            
            print env_key "=" $0
        }
    ' "$file")
    
    if [[ -n "$output" ]]; then
        echo "$result" > "$output"
    else
        echo "$result"
    fi
}

# 函数: 计算Properties文件哈希
properties_get_hash() {
    local file="$1"
    shift
    local keys=("$@")
    
    if [[ ! -f "$file" ]]; then
        return 1
    fi
    
    local hash_input=""
    
    if [[ ${#keys[@]} -eq 0 ]]; then
        # 整个文件（忽略注释和空行）
        hash_input=$(grep -vE '^[#!]|^$' "$file" 2>/dev/null | sort)
    else
        # 指定键
        for key in "${keys[@]}"; do
            local value=$(properties_read "$file" "$key")
            hash_input+="${key}=${value}"$'\n'
        done
    fi
    
    # 计算哈希
    if command -v md5sum &>/dev/null; then
        echo "$hash_input" | md5sum | awk '{print $1}'
    elif command -v cksum &>/dev/null; then
        echo "$hash_input" | cksum | awk '{print $1}'
    else
        echo "$hash_input" | wc -c
    fi
}

# 函数: 保存哈希
properties_save_hash() {
    local file="$1"
    local hash_file="${2:-${file}.hash}"
    
    local hash=$(properties_get_hash "$file")
    echo "$hash" > "$hash_file"
}

# 函数: 检查变更
properties_check_changed() {
    local file="$1"
    local hash_file="${2:-${file}.hash}"
    
    if [[ ! -f "$hash_file" ]]; then
        return 0  # 没有保存的哈希，视为已变更
    fi
    
    local old_hash=$(cat "$hash_file")
    local new_hash=$(properties_get_hash "$file")
    
    [[ "$old_hash" != "$new_hash" ]]
}

# 函数: 帮助信息
properties_help() {
    cat << 'EOF'
Java Properties配置文件操作模块

格式说明:
  - key=value 或 key:value 或 key value
  - 支持 # 或 ! 开头的注释
  - 支持层次结构: app.database.host=localhost
  - 支持多行值（行尾\续行）

函数列表:
  properties_validate <file>                验证Properties文件格式
  properties_read <file> <key> [default]    读取Properties值
  properties_write <file> <key> <value> [separator]  写入Properties值
  properties_delete <file> <key>            删除Properties键
  properties_list_keys <file> [prefix]      列出所有键
  properties_list_all <file>                列出所有键值对
  properties_merge <target> <source> [mode] 合并Properties文件
  properties_format <file> [align]          格式化Properties文件
  properties_extract <file> <prefix> [output] 提取指定前缀的配置
  properties_to_env <file> [output]         转换为环境变量格式
  properties_get_hash <file> [keys...]      计算文件哈希
  properties_save_hash <file> [hash_file]   保存哈希值
  properties_check_changed <file> [hash_file] 检查文件变更

键格式: 
  - 简单键: app_name
  - 层次键: app.database.host

示例:
  properties_read "app.properties" "app.database.host" "localhost"
  properties_write "app.properties" "app.port" "8080"
  properties_delete "app.properties" "app.debug"
  properties_format "app.properties" "true"  # 对齐等号
  properties_to_env "app.properties" "app.env"  # 转换为ENV格式

特殊功能:
  - properties_extract: 提取特定模块配置
    properties_extract "all.properties" "database" "db.properties"
  
  - properties_to_env: 转换为环境变量
    app.database.host -> APP_DATABASE_HOST

依赖: 无（纯Shell + awk实现）
EOF
}

# 日志函数（如果未定义）
if ! declare -f log_error &>/dev/null; then
    log_error() { echo "[ERROR] [$1] $2" >&2; }
    log_warn() { echo "[WARN] [$1] $2" >&2; }
    log_info() { echo "[INFO] [$1] $2" >&2; }
fi

